#!/bin/bash

# Docker容器监控脚本
# 功能：监控Docker容器和服务状态变化，通过QQ邮箱发送通知
# 作者：系统管理员
# 版本：2.0
# 日期：2025-10-21

# ===========================================
# 配置区域 - 请根据实际情况修改
# ===========================================

# 邮件配置
SMTP_SERVER="smtp.qq.com"
SMTP_PORT="465"
FROM_EMAIL=""       # 发送者QQ邮箱
EMAIL_PASSWORD=""   # QQ邮箱授权码
TO_EMAIL=""         # 接收者QQ邮箱

# 监控配置
CHECK_INTERVAL=60  # 检查间隔（秒）- 1分钟
LOG_FILE="/var/log/docker_watch.log"
STATE_FILE="/tmp/docker_containers_state"
DOCKER_SERVICE_STATE_FILE="/tmp/docker_service_state"
LOCK_FILE="/tmp/docker_watch.lock"

# ===========================================
# 函数定义区域
# ===========================================

# 日志记录函数
log_message() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

# 发送邮件函数
send_email() {
    local subject="$1"
    local body="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    log_message "INFO" "准备发送邮件: $subject"
    
    # 尝试多种发送方式
    if send_email_python "$subject" "$body"; then
        return 0
    elif send_email_curl "$subject" "$body"; then
        return 0
    elif send_email_openssl "$subject" "$body"; then
        return 0
    else
        log_message "ERROR" "所有邮件发送方式都失败了"
        return 1
    fi
}

# Python方式发送邮件
send_email_python() {
    local subject="$1"
    local body="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    # 检查Python是否可用
    local python_cmd=""
    if command -v python3 >/dev/null 2>&1; then
        python_cmd="python3"
    elif command -v python >/dev/null 2>&1; then
        python_cmd="python"
    else
        log_message "DEBUG" "Python不可用，跳过Python发送方式"
        return 1
    fi
    
    log_message "DEBUG" "尝试使用Python发送邮件"
    
    $python_cmd << EOF
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
import sys

try:
    # 创建邮件
    msg = MIMEMultipart('alternative')
    msg['Subject'] = Header('$subject', 'utf-8')
    msg['From'] = 'Docker Monitor <$FROM_EMAIL>'
    msg['To'] = '$TO_EMAIL'
    
    # HTML内容
    html_content = '''<!DOCTYPE html>
<html>
<head>
    <meta charset='UTF-8'>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .header { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
        .content { margin: 20px 0; }
        .footer { font-size: 12px; color: #666; margin-top: 20px; }
        .alert { background-color: #ffebee; border-left: 4px solid #f44336; padding: 10px; }
        .success { background-color: #e8f5e9; border-left: 4px solid #4caf50; padding: 10px; }
        .info { background-color: #e3f2fd; border-left: 4px solid #2196f3; padding: 10px; }
    </style>
</head>
<body>
    <div class='header'>
        <h2>🐳 Docker容器监控通知</h2>
        <p><strong>时间：</strong>$timestamp</p>
        <p><strong>主机：</strong>$(hostname)</p>
    </div>
    <div class='content'>
        $body
    </div>
    <div class='footer'>
        <p>此邮件由Docker监控脚本自动发送</p>
        <p>脚本位置：/opt/docker_watch.sh</p>
    </div>
</body>
</html>'''
    
    part = MIMEText(html_content, 'html', 'utf-8')
    msg.attach(part)
    
    # 创建SSL上下文
    context = ssl.create_default_context()
    
    # 连接服务器并发送
    with smtplib.SMTP_SSL('$SMTP_SERVER', $SMTP_PORT, context=context, timeout=60) as server:
        server.login('$FROM_EMAIL', '$EMAIL_PASSWORD')
        server.send_message(msg)
    
    sys.exit(0)
    
except Exception as e:
    print(f"Python发送失败: {e}", file=sys.stderr)
    sys.exit(1)
EOF

    if [ $? -eq 0 ]; then
        log_message "INFO" "Python方式邮件发送成功: $subject"
        return 0
    else
        log_message "DEBUG" "Python方式发送失败"
        return 1
    fi
}

# curl方式发送邮件
send_email_curl() {
    local subject="$1"
    local body="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    # 检查curl是否可用
    if ! command -v curl >/dev/null 2>&1; then
        log_message "DEBUG" "curl不可用，跳过curl发送方式"
        return 1
    fi
    
    log_message "DEBUG" "尝试使用curl发送邮件"
    
    local temp_file="/tmp/docker_monitor_curl_$$.tmp"
    cat > "$temp_file" << EOF
Subject: =?UTF-8?B?$(echo -n "$subject" | base64 -w 0)?=
From: Docker Monitor <$FROM_EMAIL>
To: <$TO_EMAIL>
Content-Type: text/html; charset=UTF-8
MIME-Version: 1.0

<!DOCTYPE html>
<html>
<head>
    <meta charset='UTF-8'>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .header { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
        .content { margin: 20px 0; }
        .footer { font-size: 12px; color: #666; margin-top: 20px; }
        .alert { background-color: #ffebee; border-left: 4px solid #f44336; padding: 10px; }
        .success { background-color: #e8f5e9; border-left: 4px solid #4caf50; padding: 10px; }
        .info { background-color: #e3f2fd; border-left: 4px solid #2196f3; padding: 10px; }
    </style>
</head>
<body>
    <div class='header'>
        <h2>🐳 Docker容器监控通知</h2>
        <p><strong>时间：</strong>$timestamp</p>
        <p><strong>主机：</strong>$(hostname)</p>
    </div>
    <div class='content'>
        $body
    </div>
    <div class='footer'>
        <p>此邮件由Docker监控脚本自动发送</p>
        <p>脚本位置：/opt/docker_watch.sh</p>
    </div>
</body>
</html>
EOF

    if curl --ssl-reqd \
           --url "smtps://$SMTP_SERVER:$SMTP_PORT" \
           --user "$FROM_EMAIL:$EMAIL_PASSWORD" \
           --mail-from "$FROM_EMAIL" \
           --mail-rcpt "$TO_EMAIL" \
           --upload-file "$temp_file" \
           --silent \
           --show-error \
           --max-time 60 \
           --connect-timeout 30; then
        log_message "INFO" "curl方式邮件发送成功: $subject"
        rm -f "$temp_file"
        return 0
    else
        log_message "DEBUG" "curl方式发送失败"
        rm -f "$temp_file"
        return 1
    fi
}

# OpenSSL方式发送邮件（改进版）
send_email_openssl() {
    local subject="$1"
    local body="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    log_message "DEBUG" "尝试使用OpenSSL发送邮件"
    
    local temp_dir="/tmp/docker_monitor_openssl_$$"
    mkdir -p "$temp_dir"
    
    # 创建邮件内容
    local email_file="$temp_dir/email.txt"
    cat > "$email_file" << EOF
Subject: =?UTF-8?B?$(echo -n "$subject" | base64 -w 0)?=
From: Docker Monitor <$FROM_EMAIL>
To: <$TO_EMAIL>
Content-Type: text/html; charset=UTF-8
MIME-Version: 1.0

<!DOCTYPE html>
<html>
<head>
    <meta charset='UTF-8'>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .header { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
        .content { margin: 20px 0; }
        .footer { font-size: 12px; color: #666; margin-top: 20px; }
        .alert { background-color: #ffebee; border-left: 4px solid #f44336; padding: 10px; }
        .success { background-color: #e8f5e9; border-left: 4px solid #4caf50; padding: 10px; }
        .info { background-color: #e3f2fd; border-left: 4px solid #2196f3; padding: 10px; }
    </style>
</head>
<body>
    <div class='header'>
        <h2>🐳 Docker容器监控通知</h2>
        <p><strong>时间：</strong>$timestamp</p>
        <p><strong>主机：</strong>$(hostname)</p>
    </div>
    <div class='content'>
        $body
    </div>
    <div class='footer'>
        <p>此邮件由Docker监控脚本自动发送</p>
        <p>脚本位置：/opt/docker_watch.sh</p>
    </div>
</body>
</html>
EOF

    # 编码认证信息
    local auth_user=$(echo -n "$FROM_EMAIL" | base64 -w 0)
    local auth_pass=$(echo -n "$EMAIL_PASSWORD" | base64 -w 0)
    
    # 创建SMTP命令序列（改进版）
    local smtp_file="$temp_dir/smtp_commands.txt"
    {
        echo "EHLO $(hostname)"
        echo "AUTH LOGIN"
        echo "$auth_user"
        echo "$auth_pass"
        echo "MAIL FROM:<$FROM_EMAIL>"
        echo "RCPT TO:<$TO_EMAIL>"
        echo "DATA"
        cat "$email_file"
        echo ""
        echo "."
        echo "QUIT"
    } > "$smtp_file"

    # 发送邮件
    local output_file="$temp_dir/output.txt"
    local error_file="$temp_dir/error.txt"
    
    if timeout 60 openssl s_client -connect $SMTP_SERVER:$SMTP_PORT -quiet -verify_return_error < "$smtp_file" > "$output_file" 2> "$error_file"; then
        if grep -q "250.*OK\|250 Ok\|250 Mail OK\|250.*queued" "$output_file"; then
            log_message "INFO" "OpenSSL方式邮件发送成功: $subject"
            rm -rf "$temp_dir"
            return 0
        else
            log_message "DEBUG" "OpenSSL SMTP服务器响应异常"
            log_message "DEBUG" "服务器响应: $(head -10 "$output_file" 2>/dev/null)"
        fi
    else
        log_message "DEBUG" "OpenSSL连接失败"
        log_message "DEBUG" "错误信息: $(head -5 "$error_file" 2>/dev/null)"
    fi
    
    rm -rf "$temp_dir"
    return 1
}

# 检查Docker服务状态
check_docker_service() {
    local current_status=""
    local previous_status=""
    
    # 获取当前Docker服务状态
    if systemctl is-active --quiet docker; then
        current_status="active"
    else
        current_status="inactive"
    fi
    
    # 读取上次的服务状态
    if [ -f "$DOCKER_SERVICE_STATE_FILE" ]; then
        previous_status=$(cat "$DOCKER_SERVICE_STATE_FILE")
    fi
    
    # 保存当前状态
    echo "$current_status" > "$DOCKER_SERVICE_STATE_FILE"
    
    # 检查状态变化
    if [ -n "$previous_status" ] && [ "$previous_status" != "$current_status" ]; then
        if [ "$current_status" = "inactive" ]; then
            log_message "ERROR" "Docker服务已停止"
            local service_status=$(systemctl status docker --no-pager -l 2>/dev/null || echo "无法获取状态")
            local body="<div class='alert'>
                <h3>🚨 Docker服务异常告警</h3>
                <p><strong>状态变化：</strong>运行中 → 已停止</p>
                <p><strong>检测时间：</strong>$(date '+%Y-%m-%d %H:%M:%S')</p>
                <p><strong>服务状态：</strong></p>
                <pre style='background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;'>$service_status</pre>
                <hr>
                <p><strong>建议操作：</strong></p>
                <ul>
                    <li>检查服务状态：<code>systemctl status docker</code></li>
                    <li>查看服务日志：<code>journalctl -u docker -n 50</code></li>
                    <li>重启Docker服务：<code>systemctl restart docker</code></li>
                    <li>检查磁盘空间：<code>df -h</code></li>
                </ul>
            </div>"
            send_email "🚨 Docker服务停止告警" "$body"
        elif [ "$current_status" = "active" ]; then
            log_message "INFO" "Docker服务已恢复运行"
            local body="<div class='success'>
                <h3>✅ Docker服务恢复通知</h3>
                <p><strong>状态变化：</strong>已停止 → 运行中</p>
                <p><strong>恢复时间：</strong>$(date '+%Y-%m-%d %H:%M:%S')</p>
                <p><strong>当前状态：</strong>Docker服务正常运行</p>
            </div>"
            send_email "✅ Docker服务恢复通知" "$body"
        fi
    fi
    
    # 检查Docker守护进程响应
    if [ "$current_status" = "active" ]; then
        if ! docker info >/dev/null 2>&1; then
            log_message "ERROR" "Docker守护进程无响应"
            local body="<div class='alert'>
                <h3>⚠️ Docker守护进程无响应</h3>
                <p><strong>状态：</strong>服务运行中但命令执行失败</p>
                <p><strong>检测时间：</strong>$(date '+%Y-%m-%d %H:%M:%S')</p>
                <p><strong>建议：</strong>请检查Docker守护进程状态和权限</p>
                <ul>
                    <li>重启Docker：<code>systemctl restart docker</code></li>
                    <li>检查权限：<code>ls -la /var/run/docker.sock</code></li>
                    <li>查看进程：<code>ps aux | grep docker</code></li>
                </ul>
            </div>"
            send_email "🚨 Docker守护进程无响应告警" "$body"
            return 1
        fi
    else
        return 1
    fi
    
    return 0
}

# 获取当前运行的容器列表
get_running_containers() {
    docker ps --format "{{.ID}}:{{.Names}}:{{.Image}}:{{.Status}}" 2>/dev/null || echo ""
}

# 比较容器状态变化
check_container_changes() {
    local current_containers="$1"
    local previous_containers=""
    
    # 读取上次的容器状态
    if [ -f "$STATE_FILE" ]; then
        previous_containers=$(cat "$STATE_FILE")
    fi
    
    # 保存当前状态
    echo "$current_containers" > "$STATE_FILE"
    
    # 如果是第一次运行，不进行比较
    if [ -z "$previous_containers" ]; then
        log_message "INFO" "首次运行，记录当前容器状态"
        return 0
    fi
    
    # 检查停止或消失的容器
    while IFS= read -r prev_container; do
        if [ -n "$prev_container" ]; then
            local container_id=$(echo "$prev_container" | cut -d: -f1)
            local container_name=$(echo "$prev_container" | cut -d: -f2)
            local container_image=$(echo "$prev_container" | cut -d: -f3)
            
            # 检查容器是否还在运行
            if ! echo "$current_containers" | grep -q "^$container_id:"; then
                log_message "WARN" "容器停止或消失: $container_name ($container_id)"
                
                # 获取容器详细状态
                local container_status=$(docker ps -a --filter "id=$container_id" --format "{{.Status}}" 2>/dev/null)
                if [ -z "$container_status" ]; then
                    container_status="容器已被删除"
                fi
                
                local body="<div class='alert'>
                    <h3>🛑 容器状态异常</h3>
                    <p><strong>容器名称：</strong>$container_name</p>
                    <p><strong>容器ID：</strong>$container_id</p>
                    <p><strong>镜像：</strong>$container_image</p>
                    <p><strong>当前状态：</strong>$container_status</p>
                    <p><strong>检测时间：</strong>$(date '+%Y-%m-%d %H:%M:%S')</p>
                    <hr>
                    <p><strong>建议操作：</strong></p>
                    <ul>
                        <li>检查容器日志：<code>docker logs $container_name</code></li>
                        <li>重启容器：<code>docker start $container_name</code></li>
                        <li>查看所有容器：<code>docker ps -a</code></li>
                    </ul>
                </div>"
                
                send_email "🚨 容器异常告警 - $container_name" "$body"
            fi
        fi
    done <<< "$previous_containers"
}

# 发送启动通知
send_startup_notification() {
    local current_containers="$1"
    local container_count=$(echo "$current_containers" | grep -c ":" 2>/dev/null || echo "0")
    
    # 获取系统信息
    local hostname=$(hostname)
    local docker_version=$(docker --version 2>/dev/null || echo "未知版本")
    local docker_service_status=$(systemctl is-active docker 2>/dev/null || echo "未知")
    local system_uptime=$(uptime -p 2>/dev/null || uptime)
    
    local container_list=""
    if [ "$container_count" -gt 0 ]; then
        container_list="<h4>📋 当前运行的容器详情：</h4>
        <table style='border-collapse: collapse; width: 100%; margin: 10px 0;'>
            <tr style='background-color: #f0f0f0;'>
                <th style='border: 1px solid #ddd; padding: 8px; text-align: left;'>容器名称</th>
                <th style='border: 1px solid #ddd; padding: 8px; text-align: left;'>镜像</th>
                <th style='border: 1px solid #ddd; padding: 8px; text-align: left;'>状态</th>
                <th style='border: 1px solid #ddd; padding: 8px; text-align: left;'>端口</th>
            </tr>"
        
        while IFS= read -r container; do
            if [ -n "$container" ]; then
                local container_id=$(echo "$container" | cut -d: -f1)
                local name=$(echo "$container" | cut -d: -f2)
                local image=$(echo "$container" | cut -d: -f3)
                local status=$(echo "$container" | cut -d: -f4)
                
                # 获取端口信息
                local ports=$(docker port "$container_id" 2>/dev/null | tr '\n' ', ' | sed 's/,$//' || echo "无")
                
                container_list="$container_list
                <tr>
                    <td style='border: 1px solid #ddd; padding: 8px;'><strong>$name</strong></td>
                    <td style='border: 1px solid #ddd; padding: 8px;'>$image</td>
                    <td style='border: 1px solid #ddd; padding: 8px;'>$status</td>
                    <td style='border: 1px solid #ddd; padding: 8px;'>$ports</td>
                </tr>"
            fi
        done <<< "$current_containers"
        container_list="$container_list</table>"
    else
        container_list="<div style='background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 5px;'>
            <p>⚠️ 当前没有运行中的容器</p>
        </div>"
    fi
    
    local body="<div class='success'>
        <h3>🚀 Docker监控脚本启动通知</h3>
        <div style='background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin: 10px 0;'>
            <h4>📊 系统信息</h4>
            <p><strong>主机名：</strong>$hostname</p>
            <p><strong>Docker版本：</strong>$docker_version</p>
            <p><strong>Docker服务状态：</strong>$docker_service_status</p>
            <p><strong>系统运行时间：</strong>$system_uptime</p>
            <p><strong>监控间隔：</strong>${CHECK_INTERVAL}秒 (1分钟)</p>
            <p><strong>运行容器数量：</strong>$container_count</p>
            <p><strong>日志文件：</strong>$LOG_FILE</p>
        </div>
        $container_list
        <hr>
        <div style='background-color: #e8f5e9; padding: 15px; border-radius: 5px;'>
            <h4>🛡️ 监控功能</h4>
            <ul>
                <li>🔍 每${CHECK_INTERVAL}秒检查容器状态变化</li>
                <li>📧 容器停止或宕机时发送告警邮件</li>
                <li>🚨 Docker服务停止或宕机时发送告警邮件</li>
                <li>✅ Docker服务恢复时发送恢复通知</li>
                <li>📝 详细日志记录和状态追踪</li>
                <li>🔒 防重复运行和优雅退出机制</li>
            </ul>
        </div>
    </div>"
    
    send_email "🚀 Docker监控脚本启动通知 - $hostname" "$body"
}

# 创建锁文件，防止重复运行
create_lock() {
    if [ -f "$LOCK_FILE" ]; then
        local pid=$(cat "$LOCK_FILE")
        if kill -0 "$pid" 2>/dev/null; then
            log_message "ERROR" "脚本已在运行 (PID: $pid)"
            exit 1
        else
            log_message "WARN" "发现僵尸锁文件，清理中..."
            rm -f "$LOCK_FILE"
        fi
    fi
    
    echo $$ > "$LOCK_FILE"
    log_message "INFO" "创建锁文件 (PID: $$)"
}

# 清理锁文件
cleanup() {
    log_message "INFO" "清理锁文件并退出"
    rm -f "$LOCK_FILE"
    exit 0
}

# ===========================================
# 主程序
# ===========================================

# 命令行参数处理
case "${1:-}" in
    "--test-email")
        echo "测试邮件发送功能..."
        mkdir -p "$(dirname "$LOG_FILE")"
        
        # 测试邮件内容
        test_body="<div class='info'>
            <h3>📧 邮件发送测试</h3>
            <p><strong>测试时间：</strong>$(date '+%Y-%m-%d %H:%M:%S')</p>
            <p><strong>主机名：</strong>$(hostname)</p>
            <p><strong>脚本版本：</strong>2.0</p>
            <p><strong>配置信息：</strong></p>
            <ul>
                <li>SMTP服务器：$SMTP_SERVER:$SMTP_PORT</li>
                <li>发件人：$FROM_EMAIL</li>
                <li>收件人：$TO_EMAIL</li>
            </ul>
            <p>如果您收到此邮件，说明邮件发送功能正常工作！</p>
        </div>"
        
        if send_email "🧪 Docker监控邮件测试" "$test_body"; then
            echo "✅ 测试邮件发送成功！请检查您的邮箱。"
            exit 0
        else
            echo "❌ 测试邮件发送失败！请检查配置和网络连接。"
            exit 1
        fi
        ;;
    "--debug")
        echo "调试模式：显示详细日志..."
        set -x
        ;;
    "--help"|"-h")
        echo "Docker容器监控脚本 v2.0"
        echo ""
        echo "用法: $0 [选项]"
        echo ""
        echo "选项:"
        echo "  --test-email    测试邮件发送功能"
        echo "  --debug         启用调试模式"
        echo "  --help, -h      显示此帮助信息"
        echo ""
        echo "配置文件位置: $0"
        echo "日志文件位置: $LOG_FILE"
        echo ""
        exit 0
        ;;
esac

# 设置信号处理
trap cleanup SIGTERM SIGINT

# 检查是否以root用户运行
if [ "$EUID" -ne 0 ]; then
    echo "错误：此脚本需要root权限运行"
    echo "请使用: sudo $0"
    exit 1
fi

# 创建日志目录
mkdir -p "$(dirname "$LOG_FILE")"

# 创建锁文件
create_lock

log_message "INFO" "Docker容器监控脚本启动"
log_message "INFO" "监控间隔: ${CHECK_INTERVAL}秒"
log_message "INFO" "日志文件: $LOG_FILE"

# 初始检查Docker服务
if ! check_docker_service; then
    log_message "WARN" "Docker服务初始检查失败，但继续运行监控"
fi

# 获取初始容器状态并发送启动通知
initial_containers=$(get_running_containers)
send_startup_notification "$initial_containers"
echo "$initial_containers" > "$STATE_FILE"

log_message "INFO" "开始监控循环，检查间隔: ${CHECK_INTERVAL}秒"

# 主监控循环
while true; do
    # 检查Docker服务状态
    docker_service_ok=true
    if ! check_docker_service; then
        log_message "WARN" "Docker服务检查失败，跳过容器状态检查"
        docker_service_ok=false
    fi
    
    # 只有在Docker服务正常时才检查容器状态
    if [ "$docker_service_ok" = true ]; then
        # 获取当前容器状态
        current_containers=$(get_running_containers)
        
        # 检查容器状态变化
        check_container_changes "$current_containers"
    fi
    
    # 等待下次检查
    log_message "DEBUG" "等待 ${CHECK_INTERVAL} 秒后进行下次检查..."
    sleep "$CHECK_INTERVAL"
done
